---
title: 提取组件
description: 处理复用并且保持功能优先项目的可维护性。
---

import { TipGood, TipBad } from '@/components/Tip'

<!-- Tailwind encourages a [utility-first](/docs/utility-first) workflow, where designs are initially implemented using only utility classes to avoid premature abstraction. -->
Tailwind 鼓励 [功能优先](/docs/utility-first) 的工作流程，最初仅使用功能类来实现设计以避免不成熟的抽象。

```html indigo
<template preview class="px-6 py-8">
  <div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
    <div class="md:flex">
      <div class="md:flex-shrink-0">
        <img class="h-48 w-full object-cover md:w-48" src="https://images.unsplash.com/photo-1515711660811-48832a4c6f69?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=448&q=80" width="448" height="299" alt="Man looking at item at a store">
      </div>
      <div class="p-8">
        <div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Case study</div>
        <a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">Finding customers for your new business</a>
        <p class="mt-2 text-gray-500">Getting a new business off the ground is a lot of hard work. Here are five ideas you can use to find your first customers.</p>
      </div>
    </div>
  </div>
</template>

<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
  <div class="md:flex">
    <div class="md:flex-shrink-0">
      <img class="h-48 w-full object-cover md:w-48" src="/img/store.jpg" alt="Man looking at item at a store">
    </div>
    <div class="p-8">
      <div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Case study</div>
      <a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">Finding customers for your new business</a>
      <p class="mt-2 text-gray-500">Getting a new business off the ground is a lot of hard work. Here are five ideas you can use to find your first customers.</p>
    </div>
  </div>
</div>
```

<!-- But as a project grows, you'll inevitably find yourself repeating common utility combinations to recreate the same component in many different places. This is most apparent with small components, like buttons, form elements, badges, etc. -->
但是随着项目的成长，您会不可避免的发现自己重复使用通用的功能类组合在许多不同的地方创建相同的组件。这对于类似按钮、表单元素、徽章这样的小组件最为明显。

```html emerald
<template preview>
  <div class="text-center">
    <button type="button" class="py-2 px-4 bg-emerald-500 text-white font-semibold rounded-lg shadow-md hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-emerald-400 focus:ring-opacity-75">
      Click me
    </button>
  </div>
</template>

<!-- Repeating these classes for every button can be painful -->
<button class="py-2 px-4 bg-green-500 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75">
  Click me
</button>
```

<!-- Keeping a long list of utility classes in sync across many component instances can quickly become a real maintenance burden, so when you start running into painful duplication like this, it's a good idea to extract a component. -->
在许多组件实例之间保持一长串功能类的同步很快会成为真正的维护负担，因此，当您开始遇到这种痛苦的复制时，提取组件是个好主意。

---

## 提取模板组件

<!-- It's very rare that all of the information needed to define a UI component can live entirely in CSS — there's almost always some important corresponding HTML structure you need to use as well. -->
定义一个 UI 组件的所有信息可以完全存在于 CSS 中，这非常罕见。与此同时，您也需要使用一些重要的相对应的 HTML 结构。

<!-- <TipBad>Don't rely on CSS classes to extract complex components</TipBad> -->
<TipBad>不要依赖 CSS 类来提取复杂的组件</TipBad>

```html
<template preview class="p-8">
  <div class="w-64 mx-auto">
    <img class="rounded" src="https://images.unsplash.com/photo-1452784444945-3f422708fe5e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=512&q=80" width="512" height="341" alt="Beach">
    <div class="mt-2">
      <div>
        <div class="text-xs text-gray-600 uppercase font-bold">Private Villa</div>
        <div class="font-bold text-gray-700 leading-snug">
          <a href="#" class="hover:underline">Relaxing All-Inclusive Resort in Cancun</a>
        </div>
        <div class="mt-2 text-sm text-gray-600">$299 USD per night</div>
      </div>
    </div>
  </div>
</template>

<style>
  .vacation-card { /* ... */ }
  .vacation-card-info { /* ... */ }
  .vacation-card-eyebrow { /* ... */ }
  .vacation-card-title { /* ... */ }
  .vacation-card-price { /* ... */ }
</style>

<!-- Even with custom CSS, you still need to duplicate this HTML structure -->
<div class="vacation-card">
  <img class="vacation-card-image" src="..." alt="Beach in Cancun">
  <div class="vacation-card-info">
    <div>
      <div class="vacation-card-eyebrow">Private Villa</div>
      <div class="vacation-card-title">
        <a href="/vacations/cancun">Relaxing All-Inclusive Resort in Cancun</a>
      </div>
      <div class="vacation-card-price">$299 USD per night</div>
    </div>
  </div>
</div>
```

<!-- For this reason, it's often better to extract reusable pieces of your UI into _template partials_ or _JavaScript components_ instead of writing custom CSS classes. -->
因此，通常最好将 UI 的可重用部分提取到模板片断或 JavaScript 组件中，而不是编写自定义 CSS 类。

<!-- By creating a single source of truth for a template, you can keep using utility classes without any of the maintenance burden created by duplicating the same classes in multiple places. -->
通过为模板创建一个单一源，您可以继续使用功能类，而不会因为在多个地方复制相同的类而造成任何的维护负担。

<!-- <TipGood>Create a template partial or JavaScript component</TipGood> -->
<TipGood>创建模板片断或者 JavaScript 组件</TipGood>

```html
<!-- In use -->
<VacationCard
  img="/img/cancun.jpg"
  imgAlt="Beach in Cancun"
  eyebrow="Private Villa"
  title="Relaxing All-Inclusive Resort in Cancun"
  pricing="$299 USD per night"
  url="/vacations/cancun"
/>

<!-- ./components/VacationCard.vue -->
<template>
  <div>
    <img class="rounded" :src="img" :alt="imgAlt">
    <div class="mt-2">
      <div>
        <div class="text-xs text-gray-600 uppercase font-bold">{{ eyebrow }}</div>
        <div class="font-bold text-gray-700 leading-snug">
          <a :href="url" class="hover:underline">{{ title }}</a>
        </div>
        <div class="mt-2 text-sm text-gray-600">{{ pricing }}</div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    props: ['img', 'imgAlt', 'eyebrow', 'title', 'pricing', 'url']
  }
</script>
```

<!-- The above example uses [Vue](https://vuejs.org/v2/guide/components.html), but the same approach can be used with [React components](https://reactjs.org/docs/components-and-props.html), [ERB partials](https://guides.rubyonrails.org/v5.2/layouts_and_rendering.html#using-partials), [Blade components](https://laravel.com/docs/5.8/blade#components-and-slots), [Twig includes](https://twig.symfony.com/doc/2.x/templates.html#including-other-templates), etc. -->
以上的例子使用 [Vue](https://vuejs.org/v2/guide/components.html)，但可以使用同样方式的还有 [React components](https://reactjs.org/docs/components-and-props.html), [ERB partials](https://guides.rubyonrails.org/v5.2/layouts_and_rendering.html#using-partials), [Blade components](https://laravel.com/docs/5.8/blade#components-and-slots), [Twig includes](https://twig.symfony.com/doc/2.x/templates.html#including-other-templates) 等。

---

## 使用 @apply 抽取组件类

<!-- For small components like buttons and form elements, creating a template partial or JavaScript component can often feel too heavy compared to a simple CSS class. -->
对于按钮和表单元素之类的小型组件，与简单的 CSS 类相比，创建模板片断或 JavaScript 组件通常会感觉过重。

<!-- In these situations, you can use Tailwind's `@apply` directive to easily extract common utility patterns to CSS component classes. -->
在这种情况下，您可以使用 Tailwind 的 `@apply` 指令轻松地将通用功能模块提取到 CSS 组件类中。

Here's what a `btn-indigo` class might look like using `@apply` to compose it from existing utilities:
这是一个示例，使用 `@apply` 从现有功能类中组合成 `btn-indigo` 类：


```html indigo
<template preview>
  <div class="text-center">
    <button type="button" class="py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75">
      Click me
    </button>
  </div>
</template>

<button class="**btn-indigo**">
  Click me
</button>

<style>
  **.btn-indigo** {
    @apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75;
  }
</style>
```

<!-- To avoid unintended specificity issues, we recommend wrapping your custom component styles with the `@layer components { ... }` directive to tell Tailwind what layer those styles belong to: -->
为了避免意外的特定性问题，我们建议您使用 `@layer components { ... }` 指令包装您的自定义组件样式，以告诉 Tailwind 这些样式属于哪一层。

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-blue {
    @apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
  }
}
```

<!-- Tailwind will automatically move those styles to the same place as `@tailwind components`, so you don't have to worry about getting the order right in your source files. -->
Tailwind 会将这些样式自动移到与 `@tailwind components` 相同的位置，因此您不必担心在源文件中正确放置顺序。

<!-- Using the `@layer` directive will also instruct Tailwind to consider those styles for purging when purging the `components` layer. Read our [documentation on optimizing for production](/docs/optimizing-for-production) for more details. -->
使用 `@layer` 指令还将指示 Tailwind 在清除 `components` 层时考虑使用哪些样式进行清除。

### 编写组件插件

<!-- In addition to writing component classes directly in your CSS files, you can also add component classes to Tailwind by writing your own plugin: -->
除了直接在 CSS 文件中编写组件类外，您还可以通过编写自己的插件将组件类添加到 Tailwind 中 ：

```js
// tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents, theme }) {
      const buttons = {
        '.btn': {
          padding: `${theme('spacing.2')} ${theme('spacing.4')}`,
          borderRadius: theme('borderRadius.md'),
          fontWeight: theme('fontWeight.600'),
        },
        '.btn-indigo': {
          backgroundColor: theme('colors.indigo.500'),
          color: theme('colors.white'),
          '&:hover': {
            backgroundColor: theme('colors.indigo.600')
          },
        },
      }

      addComponents(buttons)
    })
  ]
}
```

<!-- This can be a good choice if you want to publish your Tailwind components as a library or make it easier to share components across multiple projects. -->
如果您想将 Tailwind 组件发布为库，或者可以更轻松地在多个项目中共享组件，这是一个不错的选择。

<!-- Learn more in the [component plugin documentation](/docs/plugins#adding-components). -->
查看 [组件插件文档](/docs/plugins#adding-components) 了解更多。
